﻿using System;
using System.Collections.Generic;
using System.Runtime.Serialization;
using System.ComponentModel;
using System.Linq;
using System.Text;

namespace Sakura.Games.StgFramework.Builder
{
	using Res = Properties.Resources;

	/// <summary>
	/// 为可编辑的数据类型提供抽象基础类型。
	/// </summary>
	/// <remarks>
	/// 该类型提供提供对于编辑状态的标准实现。
	/// 
	/// 对一般应用情形，请重写 <see cref="CopyData"/> 和 <see cref="CreateBackupObject"/> 方法。前者请调用基类实现，并且对派生类中的新属性进行额外的复制转移操作。后者，请返回一个当前类型（您继承该类型的实际类型）的实例。
	/// 
	/// 对于复杂情形，您可以重写 <see cref="Initlialize"/>、<see cref="CleanUp"/>、<see cref="Backup"/> 以及 <see cref="Recover"/> 方法。关于上述方法的具体作用，请您阅读这些方法的说明或参考示例。
	/// </remarks>
	/// <seealso cref="IEditableObject"/>
	[Serializable]
	public abstract class EditableObject : BindableBase, IEditableObject, IDeserializationCallback, ICloneable
	{
		#region 内部状态

		/// <summary>
		/// 用于进行同步的同步对象。
		/// </summary>
		[NonSerialized]
		private object _SyncRoot;

		/// <summary>
		/// 获取用于进行同步的对象。
		/// </summary>
		protected object SyncRoot
		{
			get
			{
				return _SyncRoot;
			}
		}

		/// <summary>
		/// 用于为编辑提供数据保护的副本数据。
		/// </summary>
		[NonSerialized]
		private object _BackupData;

		/// <summary>
		/// 获取或设置用于进行备份的数据。
		/// </summary>
		protected object BackupData
		{
			get
			{
				return _BackupData;
			}
			set
			{
				_BackupData = value;
				this.OnPropertyChanged("BackupData");
			}
		}

		/// <summary>
		/// 该对象是否处于编辑状态。
		/// </summary>
		[NonSerialized]
		private bool _IsEditing;

		/// <summary>
		/// 获取或设置一个值，指示当前对象是否处于编辑模式。
		/// </summary>
		public bool IsEditing
		{
			get
			{
				return _IsEditing;
			}
			protected set
			{
				_IsEditing = value;
				this.OnPropertyChanged("IsEditing");
			}
		}

		#endregion

		#region 编辑所必须的内部操作

		/// <summary>
		/// 在两个同类型对象之间转移数据。
		/// </summary>
		/// <param name="source">作为数据源的对象。</param>
		/// <param name="target">作为数据目标的对象。</param>
		/// <remarks>
		/// 在以下情况下会调用此方法：
		/// - 在启动编辑时，将当前数据复制到备份数据（备份过程）
		/// - 在撤销编辑时，将从备份数据回复当前数据（还原过程）
		/// </remarks>
		protected virtual void CopyData(object source, object target)
		{
			return;
		}

		/// <summary>
		/// 在派生类中重写时，用于创建可用于备份的空对象实例。
		/// </summary>
		/// <returns>用于存储备份数据的新对象。</returns>
		protected abstract object CreateBackupObject();

		/// <summary>
		/// 将当前数据进行备份所需要调用的方法。
		/// </summary>
		protected virtual void Backup()
		{
			this.CopyData(this, this._BackupData);
		}

		/// <summary>
		/// 将当前数据进行还原所需要调用的方法。
		/// </summary>
		protected virtual void Recover()
		{
			this.CopyData(this._BackupData, this);
		}

		/// <summary>
		/// 在编辑前初始化数据所需要调用的方法。
		/// </summary>
		/// <remarks>
		/// 默认情况下，该方法会调用 <see cref="CreateBackupObject"/> 方法以创建一个空的备份对象并存储到 <see cref="BackupData"/> 属性中。
		/// </remarks>
		protected virtual void Initialize()
		{
			this.BackupData = this.CreateBackupObject();
		}

		/// <summary>
		/// 在编辑提交或取消后所需要调用的方法。
		/// </summary>
		/// <remarks>
		/// 默认情况下，该方法会将 <see cref="BackupData"/> 属性清空。
		/// </remarks>
		protected virtual void CleanUp()
		{
			this.BackupData = null;
		}

		/// <summary>
		/// 初始化对象并重置编辑相关的状态。
		/// </summary>
		private void ResetInternalData()
		{
			this._SyncRoot = new object();

			this._IsEditing = false;
			this._BackupData = false;
		}

		#endregion

		#region 构造方法

		/// <summary>
		/// 初始化一个对象的新实例。
		/// </summary>
		protected EditableObject()
		{
			this.ResetInternalData();
		}

		#endregion

		#region 接口实现

		/// <summary>
		/// 当开始编辑时调用。
		/// </summary>
		void IEditableObject.BeginEdit()
		{
			lock (this.SyncRoot)
			{
				if (this.IsEditing)
				{
					throw new InvalidOperationException(Res.ExObjectMustNotInEditing);
				}

				this.IsEditing = true;
				
				this.Initialize();
				this.Backup();
			}
		}

		/// <summary>
		/// 当撤销编辑时调用。
		/// </summary>
		void IEditableObject.CancelEdit()
		{
			lock (this.SyncRoot)
			{
				if (!this.IsEditing)
				{
					throw new InvalidOperationException(Res.ExObjectMustInEditing);
				}

				this.IsEditing = false;

				this.Recover();
				this.CleanUp();
			}
		}

		/// <summary>
		/// 当完成编辑时调用。
		/// </summary>
		void IEditableObject.EndEdit()
		{
			lock (this.SyncRoot)
			{
				if (!this.IsEditing)
				{
					throw new InvalidOperationException(Res.ExObjectMustInEditing);
				}

				this.IsEditing = false;

				this.CleanUp();
			}
		}

		/// <summary>
		/// 当反序列化完成时进行初始化操作。
		/// </summary>
		/// <param name="sender">消息的发出者。目前不使用该参数。</param>
		void IDeserializationCallback.OnDeserialization(object sender)
		{
			this.ResetInternalData();
		}

		/// <summary>
		/// 获得该对象的副本。
		/// </summary>
		/// <returns>该对象的副本。</returns>
		object ICloneable.Clone()
		{
			var result = this.CreateBackupObject();
			this.CopyData(this, result);
			
			return result;
		}

		#endregion

	}
}
